/* Copyright (c) 2014, 2021, Oracle and/or its affiliates.

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License, version 2.0,
   as published by the Free Software Foundation.

   This program is also distributed with certain software (including
   but not limited to OpenSSL) that is licensed under separate terms,
   as designated in a particular file or component or in included license
   documentation.  The authors of MySQL hereby grant you an additional
   permission to link the program and your derivative works with the
   separately licensed software that they have included with MySQL.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License, version 2.0, for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA */

// First include (the generated) my_config.h, to get correct platform defines.
#include "my_config.h"
#include <gtest/gtest.h>

#include "malloc_allocator.h"
#include "memroot_allocator.h"

#include <vector>
#include <list>
#include <deque>
#include <algorithm>

using std::vector;
using std::list;
using std::deque;

/*
  Tests of custom STL memory allocators.
*/


#if defined(GTEST_HAS_TYPED_TEST)

namespace stlalloc_unittest {

/*
  Wrappers to overcome the issue that we need allocators with
  default constructors for TYPED_TEST_CASE, which neither
  Malloc_allocator nor Memroot_allocator have.

  These wrappers need to inherit so that they are allocators themselves.
  Otherwise TypeParam in the tests below will be wrong.
*/
template<typename T>
class Malloc_allocator_wrapper : public Malloc_allocator<T>
{
public:
  Malloc_allocator_wrapper()
    : Malloc_allocator<T>(PSI_NOT_INSTRUMENTED)
  { }
};

template<typename T>
class Memroot_allocator_wrapper : public Memroot_allocator<T>
{
  MEM_ROOT m_mem_root;

public:
  Memroot_allocator_wrapper()
    : Memroot_allocator<T>(&m_mem_root)
  {
    init_sql_alloc(PSI_NOT_INSTRUMENTED, &m_mem_root, 1024, 0);
    // memory allocation error is expected, don't abort unit test.
    m_mem_root.error_handler= NULL;
  }

  ~Memroot_allocator_wrapper()
  {
    free_root(&m_mem_root, MYF(0));
  }
};


//
// Test of container with simple objects
//

template<typename T>
class STLAllocTestInt : public ::testing::Test
{
protected:
  T allocator;
};

typedef ::testing::Types<Malloc_allocator_wrapper<int>,
                         Memroot_allocator_wrapper<int> > AllocatorTypesInt;

TYPED_TEST_CASE(STLAllocTestInt, AllocatorTypesInt);


TYPED_TEST(STLAllocTestInt, SimpleVector)
{
  vector<int, TypeParam> v1(this->allocator);
  vector<int, TypeParam> v2(this->allocator);
  for (int i= 0; i < 100; i++)
  {
    v1.push_back(i);
    v2.push_back(100 - i);
  }

  EXPECT_EQ(100U, v1.size());
  EXPECT_EQ(100U, v2.size());

  v1.swap(v2);

  EXPECT_EQ(100U, v1.size());
  EXPECT_EQ(100U, v2.size());

  for (int i= 0; i < 100; i++)
  {
    EXPECT_EQ(i, v2[i]);
    EXPECT_EQ((100 - i), v1[i]);
  }
}


TYPED_TEST(STLAllocTestInt, SimpleList)
{
  list<int, TypeParam> l1(this->allocator);
  list<int, TypeParam> l2(this->allocator);

  for (int i= 0; i < 100; i++)
    l1.push_back(i);

  EXPECT_EQ(100U, l1.size());
  EXPECT_EQ(0U, l2.size());

  l2.splice(l2.begin(), l1);

  EXPECT_EQ(0U, l1.size());
  EXPECT_EQ(100U, l2.size());

  std::reverse(l2.begin(), l2.end());

  for (int i= 0; i < 100; i++)
  {
    EXPECT_EQ((99 - i), l2.front());
    l2.pop_front();
  }

  EXPECT_EQ(0U, l2.size());
}


#ifndef NDEBUG
TYPED_TEST(STLAllocTestInt, OutOfMemory)
{
  vector<int, TypeParam> v1(this->allocator);
  v1.reserve(10);
  EXPECT_EQ(10U, v1.capacity());

  DBUG_SET("+d,simulate_out_of_memory");
  ASSERT_THROW(v1.reserve(1000), std::bad_alloc);
}
#endif


//
// Test of container with non-trival objects
//

class Container_object;

template<typename T>
class STLAllocTestObject : public STLAllocTestInt<T>
{ };

typedef ::testing::Types<Malloc_allocator_wrapper<Container_object>,
                         Memroot_allocator_wrapper<Container_object> >
        AllocatorTypesObject;

TYPED_TEST_CASE(STLAllocTestObject, AllocatorTypesObject);

class Container_object
{
  char *buffer;

public:
  Container_object()
  {
    buffer= new char[20];
  }

  Container_object(const Container_object &other)
  {
    buffer= new char[20]; // Don't care about contents
  }

  ~Container_object()
  {
    delete[] buffer;
  }
};

TYPED_TEST(STLAllocTestObject, ContainerObject)
{
  vector<Container_object, TypeParam> v1(this->allocator);
  v1.push_back(Container_object());
  v1.push_back(Container_object());
  v1.push_back(Container_object());
}


//
// Test of container with containers
//

class Container_container;

template<typename T>
class STLAllocTestNested : public STLAllocTestInt<T>
{ };

typedef ::testing::Types<Malloc_allocator_wrapper<Container_container>,
                         Memroot_allocator_wrapper<Container_container> >
         AllocatorTypesNested;

TYPED_TEST_CASE(STLAllocTestNested, AllocatorTypesNested);

class Container_container
{
  deque<Container_object> d;

public:
  Container_container()
  {
    d.push_back(Container_object());
    d.push_back(Container_object());
  }
};

TYPED_TEST(STLAllocTestNested, NestedContainers)
{
  Container_container cc1;
  Container_container cc2;
  list<Container_container, TypeParam> l1(this->allocator);
  l1.push_back(cc1);
  l1.push_back(cc2);
}

} // namespace stlalloc_unittest

#endif // GTEST_HAS_TYPED_TEST)
